package com.nipun.neural.utils;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import com.nipun.neural.objects.Network;
import com.nipun.neural.objects.Neuron;
import com.nipun.neural.objects.networks.NetworkImpl;
import com.nipun.neural.objects.neurons.InputNeuron;
import com.nipun.neural.objects.synapses.SynapseImpl;
import com.nipun.neural.utils.io.DiskIOUtils;

public class MultiLayeredJordanNetworkCreator {
	
	private Network net;
	
	private String filePath;
	
	public MultiLayeredJordanNetworkCreator(int inputs, int[] layers, String type, String filePath){
		if(layers.length <= 0){return;}
		net = new NetworkImpl();
		this.filePath = filePath;
		// first we create all objects
		//create enough lists to hold all neurons...
		List inList = new ArrayList();
		List onList = new ArrayList();
		List mnList = new ArrayList();
		List foList = new ArrayList();
		Neuron[] inputNeurons = new Neuron[inputs+1];
		for(int i=0; i<inputs; i++){
			inputNeurons[i] = new InputNeuron("input"+i);
			net.addNode(inputNeurons[i]);
			inList.add(inputNeurons[i]);
		}
		inputNeurons[inputs] = new InputNeuron("memory");
		net.addNode(inputNeurons[inputs]);
		mnList.add(inputNeurons[inputs]);
		
		List[] hiddenLayerNeurons = new List[layers.length];
		for(int i=0; i<layers.length; i++){
			hiddenLayerNeurons[i] = new ArrayList();
			for(int j=0; j<layers[i]; j++){
				hiddenLayerNeurons[i].add(getNeuronImpl("layer"+i+""+j, type));
				net.addNode((Neuron)hiddenLayerNeurons[i].get(j));
				foList.add((Neuron)hiddenLayerNeurons[i].get(j));
			}
		}
		Neuron outputNeuron = getNeuronImpl("output", type);
		net.addNode(outputNeuron);
		foList.add(outputNeuron);
		onList.add(outputNeuron);
		//all neurons created now... add synapses to network
		for(int i=0; i<inputs+1; i++){
			for(int j=0; j<hiddenLayerNeurons[0].size(); j++){
				net.addEdge(new SynapseImpl(inputNeurons[i], (Neuron)hiddenLayerNeurons[0].get(j), 1));
			}
		}
		for(int i=0; i<layers.length-1; i++){
			List prevLayer = hiddenLayerNeurons[i];
			List nextLayer = hiddenLayerNeurons[i+1];
			for(int j=0; j<prevLayer.size(); j++){
				for (int k=0; k<nextLayer.size(); k++){
					net.addEdge(new SynapseImpl((Neuron)prevLayer.get(j), (Neuron)nextLayer.get(k), 1));
				}
			}
		}
		
		List finalHiddenLayer = hiddenLayerNeurons[layers.length-1];
		for(int i=0; i<finalHiddenLayer.size(); i++){
			net.addEdge(new SynapseImpl((Neuron)finalHiddenLayer.get(i), outputNeuron, 1));
		}
		net.addEdge(new SynapseImpl(outputNeuron, inputNeurons[inputs], 1));
		
		net.setFiringOrder(foList);
		net.setInputNodes(inList);
		net.setMemoryNodes(mnList);
		net.setOutputNodes(onList);
	}
	
	public void writeToDisk(){
		DiskIOUtils dio = new DiskIOUtils();
		dio.writeNetworkToDisk(net, filePath);
	}
	
	private Neuron getNeuronImpl(String label, String type){
		if(type == null || "".equals(type)){
			return null;
		}else {
			Neuron neuronObj = null;
			Class neuronClass;
			Constructor constructor;
			try {
				neuronClass = Class.forName(getClassName(type));
				constructor = neuronClass.getConstructor(Class.forName("java.lang.String"));
				neuronObj = (Neuron) constructor.newInstance(label);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
			if(neuronObj == null){
				System.out.println("could not find a neuron of type :: " + type);
			}
			return neuronObj;
		}
	}
		
		private String getClassName(String type){
			PropertiesManager propManager = PropertiesManager.getInstance();
			Properties props = propManager.getProperties();
			return props.getProperty(type);
		}

		public Network getNet() {
			return net;
		}

}
